using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;

namespace Sanjeet.FastCopy.FileCopyLogic
{
    public class FileSystemOperation
    {
        private const int MaxWorkers = 10;
        private readonly IList<BackgroundWorker> Workers = new List<BackgroundWorker>(MaxWorkers);
        private int count = 0;
        private string destination;
        private DateTime endTime;
        private int failure = 0;
        private string filter = "*.*";
        private BackgroundWorker listOfFilesworker = null;
        private string message;
        private bool overwrite;
        private int skipped = 0;
        private DateTime startTime;
        private bool subFolders;

        /// <summary>
        /// 
        /// </summary>
        public FileSystemOperation()
        {
            listOfFilesworker = new BackgroundWorker();
            listOfFilesworker.WorkerReportsProgress = true;
            listOfFilesworker.DoWork += new DoWorkEventHandler(listOfFilesworker_DoWork);
            listOfFilesworker.RunWorkerCompleted +=
                new RunWorkerCompletedEventHandler(listOfFilesworker_RunWorkerCompleted);
            listOfFilesworker.ProgressChanged += new ProgressChangedEventHandler(listOfFilesworker_ProgressChanged);
        }

        /// <summary>
        /// 
        /// </summary>
        public string Filter
        {
            get { return filter; }
            set { filter = value; }
        }

        /// <summary>
        /// 
        /// </summary>
        public bool SubFolders
        {
            get { return subFolders; }
            set { subFolders = value; }
        }


        /// <summary>
        /// 
        /// </summary>
        public bool Overwrite
        {
            get { return overwrite; }
            set { overwrite = value; }
        }

        /// <summary>
        /// 
        /// </summary>
        public event EventHandler<FileSystemOperationEventArg> ProgressChanged;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        private void InvokeProgressChanged(FileSystemOperationEventArg e)
        {
            EventHandler<FileSystemOperationEventArg> changed = ProgressChanged;
            if (changed != null) changed(this, e);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void listOfFilesworker_DoWork(object sender, DoWorkEventArgs e)
        {
            var info = (DirectoryInfo) e.Argument;
            if (subFolders)
                e.Result = info.GetFiles(filter, SearchOption.AllDirectories);
            else
                e.Result = info.GetFiles(filter, SearchOption.TopDirectoryOnly);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void listOfFilesworker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            message = "Retrieving the list of files...";
            InvokeProgressChanged(new FileSystemOperationEventArg(message));
            Debug.WriteLine("Change in progress in retrieving list of files...");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void listOfFilesworker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            Debug.WriteLine("Files list retrieved");
            var files = (FileInfo[]) e.Result;
            endTime = DateTime.Now;
            message = string.Format("File(s) info retrieved in {0} ms. Total {1} files to copy.",
                                    (endTime - startTime).TotalMilliseconds, files.Length);
            if (files.Length > 0)
            {
                InvokeProgressChanged(new FileSystemOperationEventArg(message));
                CreateMultipleWorkers(files.Length, files);
                startTime = DateTime.Now;
            }
            else
            {
                message = "No files to copy!";
                InvokeProgressChanged(new FileSystemOperationEventArg(message, true));
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="totalFiles"></param>
        /// <param name="files"></param>
        private void CreateMultipleWorkers(int totalFiles, object files)
        {
            int filesEachWorkerWillHandle = 0, counter = 0;
            if (totalFiles > MaxWorkers)
            {
                filesEachWorkerWillHandle = totalFiles/MaxWorkers;
                InstantiateWorkers(MaxWorkers);
                PrepareWorkers(filesEachWorkerWillHandle, counter, files);
            }
            else
            {
                InstantiateWorkers(totalFiles);
                filesEachWorkerWillHandle = 1;
                PrepareWorkers(filesEachWorkerWillHandle, counter, files);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="filesEachWorkerWillHandle"></param>
        /// <param name="counter"></param>
        /// <param name="files"></param>
        private void PrepareWorkers(int filesEachWorkerWillHandle, int counter, object files)
        {
            int start = 0;
            int end = 0;
            foreach (BackgroundWorker worker in Workers)
            {
                Debug.WriteLine("Started creating workers...");
                worker.WorkerReportsProgress = true;
                worker.DoWork += new DoWorkEventHandler(worker_DoWork);
                worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
                worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
                Debug.WriteLine("Events hooked...");
                start = filesEachWorkerWillHandle*counter++;
                Debug.WriteLine(string.Format("Start: {0}", start));
                end = start + filesEachWorkerWillHandle;
                Debug.WriteLine(string.Format("End: {0}", end));
                var data = new DataWorkerInfo(files, start, end);
                if (!worker.IsBusy)
                {
                    worker.RunWorkerAsync(data);
                    Debug.WriteLine("Async process started...");
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="count"></param>
        private void InstantiateWorkers(int count)
        {
            int limit = count > MaxWorkers ? MaxWorkers : count;
            for (int i = 0; i < limit; i++)
            {
                Workers.Add(new BackgroundWorker());
            }
            Debug.WriteLine(string.Format("{0} workers created", limit));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            endTime = DateTime.Now;
            message = string.Format("File copy completed in {0} seconds", (endTime - startTime).TotalSeconds);
            InvokeProgressChanged(new FileSystemOperationEventArg(message, true));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            message = e.UserState.ToString();
            InvokeProgressChanged(new FileSystemOperationEventArg(message));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            var data = (DataWorkerInfo) e.Argument;
            var files = (FileInfo[]) data.Data;
            var worker = (BackgroundWorker) sender;

            for (int index = data.Start; index < data.End; index++)
            {
                CopyFiles(index, files, worker);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="i"></param>
        /// <param name="files"></param>
        /// <param name="worker"></param>
        private void CopyFiles(int index, FileInfo[] files, BackgroundWorker worker)
        {
            try
            {
                string destinationDirectory = Path.GetDirectoryName(destination);
                if (!Directory.Exists(destinationDirectory)) Directory.CreateDirectory(destinationDirectory);
                files[index].CopyTo(Path.Combine(destination, files[index].Name), overwrite);
                worker.ReportProgress(0,
                                      string.Format(
                                          "{0} of size {1} bytes copied{2}{3} of {4}{2}Failures: {5}{2}Skipped:{6}",
                                          files[index].Name,
                                          files[index].Length/1024, Environment.NewLine, ++count,
                                          files.Length, failure, skipped));
            }
            catch (IOException ioEx)
            {
                worker.ReportProgress(0,
                                      string.Format("{0} already exists.{1} Skipping file.", files[index].Name,
                                                    Environment.NewLine));
                ++skipped;
            }
            catch (UnauthorizedAccessException unauthEx)
            {
                worker.ReportProgress(0,
                                      string.Format("Failed to copy: {0}{1}Error: {2}", files[index].Name,
                                                    Environment.NewLine, unauthEx.Message));
                ++failure;
                try
                {
                    worker.ReportProgress(0, "Trying alternative approach...");
                    File.Create(Path.Combine(destination, string.Format("Copy of {0}", files[index].Name)));
                }
                catch (UnauthorizedAccessException innerUnauthEx)
                {
                    worker.ReportProgress(0,
                                          string.Format(
                                              "Skipping the file. Failed to copy: {0}{1}Error: {2}",
                                              files[index].Name,
                                              Environment.NewLine, innerUnauthEx.Message));
                    ++failure;
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        public void Copy(string source, string destination)
        {
            var sourceFolder = new DirectoryInfo(source);
            var destinationFolder = new DirectoryInfo(destination);

            if (!destinationFolder.Exists)
                destinationFolder.Create();

            this.destination = destination;

            if (!sourceFolder.Exists)
                throw new ArgumentException("Source folder doesn't exist.");

            startTime = DateTime.Now;
            if (!listOfFilesworker.IsBusy)
            {
                listOfFilesworker.ReportProgress(0);
                listOfFilesworker.RunWorkerAsync(sourceFolder);
            }
        }
    }
}